package jannex.internal;

import jannex.label.LabelMap;
import jannex.sql.JxArray;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.sql.*;
import java.sql.Date;
import java.util.*;

import static java.sql.Types.*;

// TODO java8 SQLtypes
public final class SqlTypeUtils {

    private static final Map<String, Class<?>> TYPE_MAP = LabelMap.<Class<?>>builder()
            .put("CHAR", String.class)
            .put("VARCHAR", String.class)
            .put("LONGVARCHAR", String.class)
            .put("NUMERIC", BigDecimal.class)
            .put("DECIMAL", BigDecimal.class)
            .put("BIT", boolean.class)
            .put("BOOLEAN", boolean.class)
            .put("TINYINT", byte.class)
            .put("SMALLINT", short.class)
            .put("INTEGER", int.class)
            .put("BIGINT", long.class)
            .put("REAL", float.class)
            .put("FLOAT", double.class)
            .put("DOUBLE", double.class)
            .put("BINARY", byte[].class)
            .put("VARBINARY", byte[].class)
            .put("LONGVARBINARY", byte[].class)
            .put("DATE", Date.class)
            .put("TIME", Time.class)
            .put("TIMESTAMP", Timestamp.class)
            .put("CLOB", Clob.class)
            .put("BLOB", Blob.class)
            .put("ARRAY", Array.class)
            .put("DISTINCT", Object.class)
            .put("STRUCT", Struct.class)
            .put("REF", Ref.class)
            .put("DATALINK", java.net.URL.class)
            .put("JAVA_OBJECT", Object.class)
            .put("ROWID", RowId.class)
            .put("NCHAR", String.class)
            .put("NVARCHAR", String.class)
            .put("LONGNVARCHAR", String.class)
            .put("NCLOB", NClob.class)
            .put("SQLXML", SQLXML.class)
            .put("LIST", List.class)
            .put("SET", Set.class)
            .put("LIST", Collection.class)
            .build();

    private static final Map<Class<?>, String> NAME_MAP;
    private static final Map<Integer, Class<?>> TYPE_ID_MAP;

    static {
        TYPE_ID_MAP = new HashMap<>();
        TYPE_ID_MAP.put(CHAR, String.class);
        TYPE_ID_MAP.put(VARCHAR, String.class);
        TYPE_ID_MAP.put(LONGVARCHAR, String.class);
        TYPE_ID_MAP.put(NUMERIC, BigDecimal.class);
        TYPE_ID_MAP.put(DECIMAL, BigDecimal.class);
        TYPE_ID_MAP.put(BIT, boolean.class);
        TYPE_ID_MAP.put(BOOLEAN, boolean.class);
        TYPE_ID_MAP.put(TINYINT, byte.class);
        TYPE_ID_MAP.put(SMALLINT, short.class);
        TYPE_ID_MAP.put(INTEGER, int.class);
        TYPE_ID_MAP.put(BIGINT, long.class);
        TYPE_ID_MAP.put(REAL, float.class);
        TYPE_ID_MAP.put(FLOAT, double.class);
        TYPE_ID_MAP.put(DOUBLE, double.class);
        TYPE_ID_MAP.put(BINARY, byte[].class);
        TYPE_ID_MAP.put(VARBINARY, byte[].class);
        TYPE_ID_MAP.put(LONGVARBINARY, byte[].class);
        TYPE_ID_MAP.put(DATE, Date.class);
        TYPE_ID_MAP.put(TIME, Time.class);
        TYPE_ID_MAP.put(TIMESTAMP, Timestamp.class);
        TYPE_ID_MAP.put(CLOB, Clob.class);
        TYPE_ID_MAP.put(BLOB, Blob.class);
        TYPE_ID_MAP.put(ARRAY, Array.class);
        TYPE_ID_MAP.put(DISTINCT, Object.class);
        TYPE_ID_MAP.put(STRUCT, Struct.class);
        TYPE_ID_MAP.put(REF, Ref.class);
        TYPE_ID_MAP.put(DATALINK, java.net.URL.class);
        TYPE_ID_MAP.put(JAVA_OBJECT, Object.class);
        TYPE_ID_MAP.put(ROWID, RowId.class);
        TYPE_ID_MAP.put(NCHAR, String.class);
        TYPE_ID_MAP.put(NVARCHAR, String.class);
        TYPE_ID_MAP.put(LONGNVARCHAR, String.class);
        TYPE_ID_MAP.put(NCLOB, NClob.class);
        TYPE_ID_MAP.put(SQLXML, SQLXML.class);

        NAME_MAP = new HashMap<>();
        NAME_MAP.put(String.class, "VARCHAR");
        NAME_MAP.put(BigDecimal.class, "NUMERIC");
        NAME_MAP.put(Boolean.class, "BOOLEAN");
        NAME_MAP.put(boolean.class, "BOOLEAN");
        NAME_MAP.put(Byte.class, "TINYINT");
        NAME_MAP.put(byte.class, "TINYINT");
        NAME_MAP.put(Short.class, "SMALLINT");
        NAME_MAP.put(short.class, "SMALLINT");
        NAME_MAP.put(Integer.class, "INTEGER");
        NAME_MAP.put(int.class, "INTEGER");
        NAME_MAP.put(Long.class, "BIGINT");
        NAME_MAP.put(long.class, "BIGINT");
        NAME_MAP.put(Float.class, "REAL");
        NAME_MAP.put(float.class, "REAL");
        NAME_MAP.put(Double.class, "DOUBLE");
        NAME_MAP.put(double.class, "DOUBLE");
        NAME_MAP.put(byte[].class, "VARBINARY");
        NAME_MAP.put(Date.class, "DATE");
        NAME_MAP.put(Time.class, "TIME");
        NAME_MAP.put(Timestamp.class, "TIMESTAMP");
        NAME_MAP.put(Clob.class, "CLOB");
        NAME_MAP.put(Blob.class, "BLOB");
        NAME_MAP.put(Array.class, "ARRAY");
        NAME_MAP.put(Struct.class, "STRUCT");
        NAME_MAP.put(Ref.class, "REF");
        NAME_MAP.put(URL.class, "DATALINK");
        NAME_MAP.put(RowId.class, "ROWID");
        NAME_MAP.put(NClob.class, "NCLOB");
        NAME_MAP.put(SQLXML.class, "SQLXML");
        NAME_MAP.put(List.class, "LIST");
        NAME_MAP.put(Set.class, "SET");
        NAME_MAP.put(Collection.class, "LIST");
    }

    public static String nameOfType(Class<?> typeClass) {
        if (NAME_MAP.containsKey(typeClass)) {
            return NAME_MAP.get(typeClass);
        } else if (typeClass.isArray()) {
            return "ARRAY";
        } else {
            for (Map.Entry<Class<?>, String> entry : NAME_MAP.entrySet()) {
                if (entry.getKey().isAssignableFrom(typeClass)) {
                    return entry.getValue();
                }
            }
        }
        return "JAVA_OBJECT";
    }

    public static Class<?> typeOf(String typeName) {
        if (TYPE_MAP.containsKey(typeName)) {
            return TYPE_MAP.get(typeName);
        }
        return Object.class;
    }

    public static int sqlTypeOf(Class<?> type) {
        if (type.isArray()) {
            if (type.getComponentType().equals(Byte.TYPE)) {
                return BINARY;
            }
            return ARRAY;
        } else if (type.equals(String.class)) {
            return VARCHAR;
        } else if (type.equals(long.class) || type.equals(Long.class)) {
            return BIGINT;
        } else if (type.equals(int.class) || type.equals(Integer.class)) {
            return INTEGER;
        } else if (type.equals(short.class) || type.equals(Short.class)) {
            return SMALLINT;
        } else if (type.equals(byte.class) || type.equals(Byte.class)) {
            return TINYINT;
        } else if (type.equals(boolean.class) || type.equals(Boolean.class)) {
            return BOOLEAN;
        } else if (type.equals(char.class) || type.equals(Character.class)) {
            return CHAR;
        } else if (type.equals(float.class) || type.equals(Float.class)) {
            return FLOAT;
        } else if (type.equals(double.class) || type.equals(Double.class)) {
            return DOUBLE;
        } else if (type.equals(Date.class) || type.equals(Calendar.class) || type.equals(Timestamp.class)) {
            return TIMESTAMP;
        } else if (type.equals(java.sql.Date.class)) {
            return DATE;
        } else if (type.equals(Time.class)) {
            return TIME;
        } else if (type.equals(BigDecimal.class)) {
            return DECIMAL;
        } else if (type.equals(Number.class) || type.equals(BigInteger.class)) {
            return NUMERIC;
        }
        return OTHER;
    }

    public static Map<String, Class<?>> getDefaultTypeMap() {
        return TYPE_MAP;
    }
}
